/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "CommServer.h"
#include "CommChannel.h"
#include "myassert.h"
#include "Session.h"
#include "StringBuilder.h"
#include "color.h"

using namespace Chronos;
using namespace std;


#define FOREACH_CHANNEL for (ChannelsConstIter iter=channels.begin(); iter!=channels.end(); ++iter) 

#define FIND_CHANNEL(session, name) \
    CommChannel* channel = findChannel(name); \
    if (channel == 0) { \
        session.write() << "Unknown channel.\n"; \
        return; \
    } 

CommServer::CommServer(EventServer& eventServer_)
	: eventServer(eventServer_)
{
	eventServer.registerListener(Event::PLAYER_LOGIN, (EventListener*)this);
	eventServer.registerListener(Event::PLAYER_LOGOUT, (EventListener*)this);
}


CommServer::~CommServer() {
    eventServer.unregisterListener((EventListener*)this);
    FOREACH_CHANNEL delete iter->second; 
}


void CommServer::notify(Event::EventType event, Session& session) {
    switch(event) {
	case Event::PLAYER_LOGIN:
		addPlayerToAllChannels(session);
		break;
	case Event::PLAYER_LOGOUT:
		removePlayerFromAllChannels(session);  
		break;
	default:
		break;
    }
}


void CommServer::addChannel(const string& name, const char* color) {
	if (findChannel(name) != 0) ASSERT_FAIL();
	channels[name] = new CommChannel(name, color);
}


void CommServer::addPlayerChannel(Session& session, const string& channelName, const char* color) {
    CommChannel* channel = findChannel(channelName);
    if (channel != 0) {
        session.write() << "Channel " << channel->getFullName() << " already exists.\n";
        return;
    }
    channel = new CommChannel(channelName, color, false);
    channels[channelName] = channel;
    session.write() << "Channel " << channel->getFullName() << " created.\n";
    channel->addMember(session);
}


CommChannel* CommServer::findChannel(const string& name) const {
	ChannelsConstIter iter = channels.find(name);
	if (iter != channels.end()) {
		return iter->second;
	}
	return 0;
}


void CommServer::addPlayerToAllChannels(Session& session) {
    FOREACH_CHANNEL {
        if (iter->second->isPermanent()) iter->second->addMember(session);
    }
}


void CommServer::removePlayerFromAllChannels(Session& session) {
    ChannelsIter iter = channels.begin();
    while (iter!=channels.end()) {
        CommChannel* channel = iter->second;
        ++iter;
        channel->removeIfMember(session);
        checkForRemoval(session, channel->getName());
    }
}


void CommServer::checkForRemoval(Session& session, const string& channelName) {
	ChannelsIter iter = channels.find(channelName);
	ASSERT_TRUE(iter != channels.end());
    CommChannel* channel = iter->second; 
    if (!channel->isPermanent() && channel->isEmpty()) {
        session.write() << "Channel " << channel->getFullName() << " destroyed.\n";
        channels.erase(iter++);
        delete channel;
    }
}


void CommServer::write(Session& session, const string& channelName, const string& msg) {
    FIND_CHANNEL(session, channelName);
    channel->write(session, msg); 
}


void CommServer::listChannels(Session& session) const {
    session.write() << "Channels:\n";
    FOREACH_CHANNEL iter->second->writeName(session);

}


void CommServer::join(Session& session, const string& channelName) {
    FIND_CHANNEL(session, channelName);
    channel->addMember(session);
}


void CommServer::leave(Session& session, const string& channelName) {
    FIND_CHANNEL(session, channelName);
    channel->removeMember(session);
    checkForRemoval(session, channelName);
}


void CommServer::showChannelMembers(Session& session, const string& channelName) const {
    FIND_CHANNEL(session, channelName);
    channel->showMembers(session);
}

